<?php

class GenericModel {
	
	private $tableName;
	private $conn;
	public $rows;
	public $offset;
	
	public function __construct(){
		$this->conn = Conexao::getInstance();
		// Pega os dados para paginação
		$page = Uri::getPOST('page');
		$page = $page ? $page : 1;
		$rows = Uri::getPOST('rows');
		$this->rows = $rows ? $rows : 10;
		$this->offset = ($page-1)*$this->rows;
	}
	
	/**
	 * Define a tabela que sera usada.
	 * @param String $tableName
	 */
	public function setTableName($tableName){
		$this->tableName = 	$this->avoidInject($tableName);
	}
	
	public function getTableName($tableName = NULL){
		if(is_null($tableName)){
			return $this->tableName;
		}else{
			return $this->avoidInject($tableName);
		}
	}
	/**
	 * Retorna o numero total de registros de uma tabela
	 * @param string Table Name
	 * @return inteiro contento o total de registros
	 */
	public function getTotal($tableName = NULL){
		$total = $this->query("SELECT COUNT(0) as total FROM ".$this->tableName. " WHERE ativo = 1", NULL, TRUE);
		return is_null($total) ? 0 : $total['total'];
	}
	
	/**
	* Evita que o componente de query fornecido contenha SQL inject.
	* Utilizado para concatenar campos e nomes de tabelas provinientes
	* de requisicoes AJAX.
	* @param $element Nome da tabela ou coluna.
	*/
	private function avoidInject($element) {
		if (preg_match("/^[a-zA-Z]+[a-zA-Z_0-9]*\$/i", $element)) {
			return $element;
		}
	
		return NULL;
	}
  
  protected function getFields($object){
    $fields = '';
    $delim = ", ";
    foreach ($object as $k => $v) {
      $fields .= $delim . $k;
    }
    return $fields;
  } 
	
  public function select($object, $tableName = NULL, $uniqueResult = FALSE) {
    $tableName = $this-> getTableName($tableName);
    
    if(is_array($object)){
      $ID = isset($object["id"]) ? $object["id"] : null;
    }else{
      $ID = isset($object->id) ? $object->id : null;
    }
    $query = "SELECT * FROM {$tableName}";
    if($ID){
      $query .= " WHERE id = ?";
    }
    
    return $this->query($query, array($ID), $uniqueResult);
  }
  
	/**
	* Saval/atualiza o objeto da tabela fornecIDa.
	* @param string $tableName Nome da tabela.
	* @param object $object Objeto que sera salvo na base de dados.
	*/
	public function saveOrUpdate($object, $tableName = NULL) {
		$tableName = $this->getTableName($tableName);
	
		if(is_array($object)){
			$ID = isset($object["id"]) ? $object["id"] : null;
		}else{
			$ID = isset($object->id) ? $object->id : null;
		}
		if ($ID) {
			$stmt = $this->execQuery("SELECT id FROM " . $tableName . " WHERE id = ?", array($ID));
			if ($stmt->fetchAll()) {
				return $this->update($object, $tableName);
			}
		}
	
		return $this->insert($object, $tableName);
	}
	
	/**
	* Insere o objeto fornecido.
	* @param string $tableName Nome da tabela onde esta localizado o objeto fornecIDo.
	* @param object $object Objeto que sera Inserido.
	*/
	public function insert($object, $tableName = NULL){
		
		$tableName = $this->getTableName($tableName);

		$fields = "";
		$values = "";
		$params = array();
		
		$delim = "";
		
		// Montando o script de insert
		foreach ($object as $k => $v) {
				
			// O ID e auto incremento
			if ($k == "id") {
				continue;
			}
				
			$v = empty($v) ? $v : trim($v);
				
			if($v === "" || $v === NULL){
				continue;
			}
				
			$fields .= $delim . $this->avoidInject($k);
			$values .= $delim . "?";
				
			$delim = ", ";
				
			$params[] = $v;
		}
		
		$this->execQuery("INSERT INTO {$tableName} ({$fields}) VALUES ($values)", $params) ;

		if(is_array($object)){
			$object["id"] = $this->conn->lastInsertId();
		}else{
			$object->id = $this->conn->lastInsertId();
		}
		
		return $object;
	}
	
	/**
	* Atualiza o objeto fornecido.
	* @param string $tableName Nome da tabela onde esta localizado o objeto fornecIDo.
	* @param object $object Objeto que sera atualizado.
	*/
	public function update($object, $tableName = NULL) {
		
		$tableName = $this->getTableName($tableName);
  	
		$fields = "";
		$params = array();

		$delim = "";
	
		// Montando o script de update
		foreach ($object as $k => $v) {
				
			// O ID e auto incremento
			if ($k == "id") {
				continue;
			}
				
			$v = empty($v) ? $v : trim($v);
	
			if($v === ''){
				$v = NULL;
			}
				
			$fields .= $delim . $this->avoidInject($k) . " = ?";
			$params[] = $v;
				
			$delim = ", ";
		}
		//$params[] = $object["id"];
		
		if(is_array($object)){
			$params[] = $object["id"];
		}else{
			$params[] = $object->id;
		}
		$sql = "UPDATE {$tableName} SET {$fields} WHERE id = ?";
	
		$this->execQuery($sql, $params);
		return $object;
	}
	
	/**
	* Exclui o registro para a tabela fornecida.
	* @param string $tableName Nome da tabela.
	* @param number $ID ID do registro
	*/
	public function delete($ID, $tableName = NULL) {
		$tableName = $this->getTableName($tableName);
	
		$sql = "DELETE FROM {$tableName} WHERE id= ?";
	
		$this->execQuery($sql, array($ID));
	}
	
	/**
	 * Realiza uma exclusao em lote da tabela fornecida.
	 * @param string $tableName Nome da tabela
	 * @param array $IDs IDs dos registros que serao excluidos
	 */
	public function batchDelete($IDs, $tableName = NULL) {
		foreach ($IDs as $ID) {
			$this->delete($tableName, $ID);
		}
	}
	
	/**
	 * Realiza a consulta sql fornecida.
	 * @param $sqlQuery SQL que ser� executado.
	 * @param $params Parametros da consulta que ser� executada.
	 * @param $uniqueResult Indica se a consulta retornara um resultado unico.
	 * @return O resultado da consulta fornecida.
	 */
	public function query($sqlQuery, $params = NULL, $uniqueResult = false) {
		$stmt = $this->execQuery($sqlQuery, $params);
		$ret = $stmt->fetchAll();

		if ($uniqueResult) {
			return count($ret) > 0 ? $ret[0] : NULL;
		}

		return count($ret) > 0 ? $ret : NULL;
	}
	
	/**
	 * Retorna as colunas de uma tabela
	 * @param unknown_type $tableName
	 * @return Ambigous <O, NULL, unknown, multitype:>
	 */
	private function getColumns($tableName){
		return $this->query("SHOW COLUMNS FROM {$tableName}");
	}
	
	/**
	 * Filtra o objeto pelas colunas da tabela.
	 * @param $tableName Nome da tabela fornecido.
	 * @param $object $objeto que sera comparado com as colunas da tabela.
	 * @return Retorna array filtrado pelas colunas.
	 */
	public function filterObject($object, $tableName){
		$columns = $this->getColumns($tableName);
	
		$key 	= array();
		$values = array();
			
		foreach($columns as $col){
			if(array_key_exists($col['Field'], $object)){
				array_push($key, $col['Field']);
				array_push($values, $object[$col['Field']]);
			}
		}
	
		$obj = array_combine($key, $values);
			
		return $obj;
	}

	/**
	 * Executa sql de comandos de altera��o da base
	 * @param $sqlQuery SQL que ser� executado.
	 * @param $params Parametros da consulta que ser� executada.
	 * @return O numero de linhas afetadas com a execucao do comando sql.
	 */
	public function execute($sqlQuery, $params) {
		$stmt = $this->execQuery($sqlQuery, $params);
		return $stmt->rowCount();
	}

	/**
	 *
	 * Classe Responsavel por executar a query utilizando PDO
	 * @param  String $statement, query a ser executada
	 * @param Array $params, parametros passados ( bindValue ) para o PDO
	 * @param Boolean $log, se passar true vai existir
	 * @return PDOStatement
	 */
	private function execQuery($statement, $params = null, $log = false) {
		
			$stmt = $this->conn->prepare($statement);
			$stmt->setFetchMode(PDO::FETCH_ASSOC);
			
			/* PARA LOGS DE QUERY NO SISTEMA (TESTE) */
			if($log){
				$LoggerDB = new LoggerDB();
				$LoggerDB->write($stmt->queryString);
			}
			if ($params) {
				$stmt->execute($params);
			} else {
				$stmt->execute();
			}
			return $stmt;
	}
	
	public function getLastInsertId(){
		return $this->conn->lastInsertId();
	}
	
	/**
	 * Inicia uma transa��o
	 */
	public function transaction(){
		$this->conn->beginTransaction();
	}
	
	/**
	 * Commita uma transa��o se for concluido com sucesso
	 */
	public function commit(){
		$this->conn->commit();
	}
	
	/**
	 * Rollback nas instru��es para n�o poder ter
	 */
	public function rollback(){
		$this->conn->rollBack();
	}
}

?>
